﻿/*******************************************************************************
 * AAEngine
 * Copyright (c) 2010 Mike Jarosch
 *
 * Original source:
 * Copyright (C) 2011 Microsoft Corporation
 *
 * This file is licensed under the terms of the  Microsoft Permissive License 
 * (Ms-PL), which is included in the "Microsoft Permissive License.rtf" file at
 * the root directory of this SDK.
 ******************************************************************************/

using System;
using System.Collections.Generic;
using System.Xml.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using AAEngine.Engine.Core;
using AAEngine.Engine.Debug;
using AAEngine.Engine.Serialization;

namespace AAEngine.Engine
{
    public class InputMap : ITickedObject, IDisposable, ISerializable
    {
        private PlayerIndex? _playerIndex = null;
//        private InputState _inputState = new InputState();
        private Dictionary<InputKey, string> _keyMap = new Dictionary<InputKey, string>();
        private Dictionary<string, Action<float>> _actionMap = new Dictionary<string, Action<float>>();
        private float _lastMouseX;
        private float _lastMouseY;
        private int _lastMouseWheel;
        private float _lastLeftThumbX;
        private float _lastLeftThumbY;
        private float _lastRightThumbX;
        private float _lastRightThumbY;
        private float _lastLeftTrigger;
        private float _lastRightTrigger;
        private bool _suppressDeltaNextTime = false;
        private bool _isFirstUpdate = true;

        /// <summary>
        /// serializes the InputMap to a format containing just key value pairs with
        /// the key representing the name of an input event and the value representing
        /// the name of the key from the InputKey class.
        /// </summary>
        /// <param name="node"></param>
        public void Serialize(XElement node)
        {
            foreach (InputKey keyCode in _keyMap.Keys)
            {
                node.AddElement(_keyMap[keyCode]).Value = InputKey.NameForInputKey(keyCode);
            }
        }

        /// <summary>
        /// deserializes the InputMap from the format described in the serialize method.
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        public object Deserialize(XElement node)
        {
            foreach (XElement child in node.Elements())
            {
                MapKeyToAction(InputKey.InputKeyForName(child.Value), child.Name.LocalName);
            }

            return this;
        }

        public InputMap()
        {
            AAGame.ProcessManager.AddTickedObject(this, Int32.MaxValue);
        }

        public InputMap(PlayerIndex? playerIndex)
        {
            _playerIndex = playerIndex;
            AAGame.ProcessManager.AddTickedObject(this, Int32.MaxValue);
        }

        public void OnTick(float deltaTime)
        {
            PlayerIndex pi;

            foreach (Keys key in TypeUtility.GetEnumValues(typeof(Keys)))
            {
                if (AAGame.InputManager.IsNewKeyPress(key, _playerIndex, out pi))
                {
                    OnInput(InputKey.InputKeyForKeys(key), 1f);
                }
                if (AAGame.InputManager.IsNewKeyRelease(key, _playerIndex, out pi))
                {
                    OnInput(InputKey.InputKeyForKeys(key), 0f);
                }
            }

            foreach (Buttons button in TypeUtility.GetEnumValues(typeof(Buttons)))
            {
                if (AAGame.InputManager.IsNewButtonPress(button, _playerIndex, out pi))
                {
                    OnInput(InputKey.InputKeyForButtons(button), 1f);
                }
                if (AAGame.InputManager.IsNewButtonRelease(button, _playerIndex, out pi))
                {
                    OnInput(InputKey.InputKeyForButtons(button), 0f);
                }
            }

            Vector2 leftThumbstick = AAGame.InputManager.LeftThumbstick(_playerIndex, out pi);
            Vector2 rightThumbstick = AAGame.InputManager.RightThumbstick(_playerIndex, out pi);
            float leftTrigger = AAGame.InputManager.LeftTrigger(_playerIndex, out pi);
            float rightTrigger = AAGame.InputManager.RightTrigger(_playerIndex, out pi);
            float mouseX = AAGame.InputManager.CurrentMouseState.X;
            float mouseY = AAGame.InputManager.CurrentMouseState.Y;
            int mouseWheel = AAGame.InputManager.CurrentMouseState.ScrollWheelValue;

            if (_isFirstUpdate)
            {
                _lastLeftThumbX = leftThumbstick.X;
                _lastLeftThumbY = leftThumbstick.Y;
                _lastLeftTrigger = leftTrigger;
                _lastMouseWheel = mouseWheel;
                _lastMouseX = mouseX;
                _lastMouseY = mouseY;
                _lastRightThumbX = rightThumbstick.X;
                _lastRightThumbY = rightThumbstick.Y;
                _lastRightTrigger = rightTrigger;
                _isFirstUpdate = false;
                return;
            }

            UpdateAxis(InputKey.THUMBSTICK_LEFT_X, InputKey.THUMBSTICK_LEFT_X_DELTA, _lastLeftThumbX, leftThumbstick.X);
            UpdateAxis(InputKey.THUMBSTICK_LEFT_Y, InputKey.THUMBSTICK_LEFT_Y_DELTA, _lastLeftThumbY, leftThumbstick.Y);
            UpdateAxis(InputKey.THUMBSTICK_RIGHT_X, InputKey.THUMBSTICK_RIGHT_X_DELTA, _lastRightThumbX, rightThumbstick.X);
            UpdateAxis(InputKey.THUMBSTICK_RIGHT_Y, InputKey.THUMBSTICK_RIGHT_Y_DELTA, _lastRightThumbY, rightThumbstick.Y);

            UpdateAxis(InputKey.TRIGGER_LEFT, InputKey.TRIGGER_LEFT_DELTA, _lastLeftTrigger, leftTrigger);
            UpdateAxis(InputKey.TRIGGER_RIGHT, InputKey.TRIGGER_RIGHT_DELTA, _lastRightTrigger, rightTrigger);

            UpdateAxis(InputKey.MOUSE_WHEEL, InputKey.MOUSE_WHEEL_DELTA, _lastMouseWheel, mouseWheel);
            UpdateAxis(InputKey.MOUSE_X, InputKey.MOUSE_X_DELTA, _lastMouseX, mouseX);
            UpdateAxis(InputKey.MOUSE_Y, InputKey.MOUSE_Y_DELTA, _lastMouseY, mouseY);

            // TODO: Clean this up
            if (AAGame.InputManager.LastMouseState.LeftButton == ButtonState.Released
                && AAGame.InputManager.CurrentMouseState.LeftButton == ButtonState.Pressed)
            {
                OnInput(InputKey.MOUSE_BUTTON1, 1);
            }
            if (AAGame.InputManager.LastMouseState.LeftButton == ButtonState.Pressed
                && AAGame.InputManager.CurrentMouseState.LeftButton == ButtonState.Released)
            {
                OnInput(InputKey.MOUSE_BUTTON1, 0);
            }

            if (AAGame.InputManager.LastMouseState.MiddleButton == ButtonState.Released
                && AAGame.InputManager.CurrentMouseState.MiddleButton == ButtonState.Pressed)
            {
                OnInput(InputKey.MOUSE_BUTTON2, 1);
            }
            if (AAGame.InputManager.LastMouseState.MiddleButton == ButtonState.Pressed
                && AAGame.InputManager.CurrentMouseState.MiddleButton == ButtonState.Released)
            {
                OnInput(InputKey.MOUSE_BUTTON2, 0);
            }

            if (AAGame.InputManager.LastMouseState.RightButton == ButtonState.Released
                && AAGame.InputManager.CurrentMouseState.RightButton == ButtonState.Pressed)
            {
                OnInput(InputKey.MOUSE_BUTTON3, 1);
            }
            if (AAGame.InputManager.LastMouseState.RightButton == ButtonState.Pressed
                && AAGame.InputManager.CurrentMouseState.RightButton == ButtonState.Released)
            {
                OnInput(InputKey.MOUSE_BUTTON3, 0);
            }

            if (AAGame.InputManager.LastMouseState.XButton1 == ButtonState.Released
                && AAGame.InputManager.CurrentMouseState.XButton1 == ButtonState.Pressed)
            {
                OnInput(InputKey.MOUSE_BUTTON4, 1);
            }
            if (AAGame.InputManager.LastMouseState.XButton1 == ButtonState.Pressed
                && AAGame.InputManager.CurrentMouseState.XButton1 == ButtonState.Released)
            {
                OnInput(InputKey.MOUSE_BUTTON4, 0);
            }

            if (AAGame.InputManager.LastMouseState.XButton2 == ButtonState.Released
                && AAGame.InputManager.CurrentMouseState.XButton2 == ButtonState.Pressed)
            {
                OnInput(InputKey.MOUSE_BUTTON4, 1);
            }
            if (AAGame.InputManager.LastMouseState.XButton2 == ButtonState.Pressed
                && AAGame.InputManager.CurrentMouseState.XButton2 == ButtonState.Released)
            {
                OnInput(InputKey.MOUSE_BUTTON4, 0);
            }


            _lastLeftThumbX = leftThumbstick.X;
            _lastLeftThumbY = leftThumbstick.Y;
            _lastLeftTrigger = leftTrigger;
            _lastMouseWheel = mouseWheel;
            _lastMouseX = mouseX;
            _lastMouseY = mouseY;
            _lastRightThumbX = rightThumbstick.X;
            _lastRightThumbY = rightThumbstick.Y;
            _lastRightTrigger = rightTrigger;
        }

        public void MapKeyToAction(InputKey key, string action)
        {
            _keyMap[key] = action;
        }

        public void MapActionToHandler(string action, Action<float> handler)
        {
            _actionMap[action] = handler;
        }

        public void MapKeyToHandler(InputKey key, Action<float> handler)
        {
            string action = InputKey.NameForInputKey(key);
            MapKeyToAction(key, action);
            MapActionToHandler(action, handler);
        }

        public void Dispose()
        {
            AAGame.ProcessManager.RemoveTickedObject(this);
        }

        private void OnInput(InputKey key, float value)
        {
            if (!_keyMap.ContainsKey(key))
            {
                return;
            }

            string action = _keyMap[key];
            if (!_actionMap.ContainsKey(action))
            {
                Logger.Print(this, "Got an action for '" + action + "' but no registered callback; ignoring");
                return;
            }

            _actionMap[action](value);
        }

        private void UpdateAxis(InputKey key, InputKey delta, float oldValue, float newValue)
        {
            if (oldValue != newValue)
            {
                OnInput(key, newValue);
                OnInput(delta, newValue - oldValue);
            }

        }
    }
}
